/*
 *  Miscellaneous routines implementation for MSDOS filesystem
 *
 *  Copyright (C) 2001 OKTET Ltd., St.-Petersburg, Russia
 *  Author: Eugeny S. Mints <Eugeny.Mints@oktet.ru>
 *
 *  The license and distribution terms for this file may be
 *  found in the file LICENSE in this distribution or at
 *  http://www.OARcorp.com/rtems/license.html.
 *
 *  @(#) $Id: msdos_misc.c,v 1.20 2002/02/11 16:42:14 jack Exp $
 */

#if HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdlib.h>
#include <sys/time.h>
#include <unistd.h>
#include <rtems/libio_.h>

#include "fat.h"
#include "fat_fat_operations.h"
#include "fat_file.h"

#include "msdos.h"

/* This copied from Linux */
static int day_n[] = { 0,31,59,90,120,151,181,212,243,273,304,334,0,0,0,0 };
		  /* JanFebMarApr May Jun Jul Aug Sep Oct Nov Dec */

#undef CONFIG_ATARI

/* MS-DOS "device special files" */
static const char *reserved_names[] = {
#ifndef CONFIG_ATARI /* GEMDOS is less stupid */
    "CON     ","PRN     ","NUL     ","AUX     ",
    "LPT1    ","LPT2    ","LPT3    ","LPT4    ",
    "COM1    ","COM2    ","COM3    ","COM4    ",
#endif
    NULL };

static char bad_chars[] = "*?<>|\"";
#ifdef CONFIG_ATARI
/* GEMDOS is less restrictive */
static char bad_if_strict[] = " ";
#else
static char bad_if_strict[] = "+=,; ";
#endif

/* The following three functions copied from Linux */
/*
 * Formats an MS-DOS file name. Rejects invalid names
 * 
 * conv is relaxed/normal/strict, name is proposed name,
 * len is the length of the proposed name, res is the result name,
 * dotsOK is if hidden files get dots.
 */
int 
msdos_format_name(char conv, const char *name, int len, char *res, 
                  char dotsOK)
{
	char *walk;
	const char **reserved;
	unsigned char c;
	int space;
	if (name[0] == '.') {  /* dotfile because . and .. already done */
		if (!dotsOK) return -EINVAL;
		/* Get rid of dot - test for it elsewhere */
		name++; len--;
	}
#ifndef CONFIG_ATARI
	space = 1; /* disallow names that _really_ start with a dot */
#else
	space = 0; /* GEMDOS does not care */
#endif
	c = 0;
	for (walk = res; len && walk-res < 8; walk++) {
	    	c = *name++;
		len--;
		if (conv != 'r' && strchr(bad_chars,c)) return -EINVAL;
		if (conv == 's' && strchr(bad_if_strict,c)) return -EINVAL;
  		if (c >= 'A' && c <= 'Z' && conv == 's') return -EINVAL;
		if (c < ' ' || c == ':' || c == '\\') return -EINVAL;
/*  0xE5 is legal as a first character, but we must substitute 0x05     */
/*  because 0xE5 marks deleted files.  Yes, DOS really does this.       */
/*  It seems that Microsoft hacked DOS to support non-US characters     */
/*  after the 0xE5 character was already in use to mark deleted files.  */
		if((res==walk) && (c==0xE5)) c=0x05;
		if (c == '.') break;
		space = (c == ' ');
		*walk = (c >= 'a' && c <= 'z') ? c-32 : c;
	}
	if (space) return -EINVAL;
	if (conv == 's' && len && c != '.') {
		c = *name++;
		len--;
		if (c != '.') return -EINVAL;
	}
	while (c != '.' && len--) c = *name++;
	if (c == '.') {
		while (walk-res < 8) *walk++ = ' ';
	 	while (len > 0 && walk-res < MSDOS_NAME_MAX) {
			c = *name++;
			len--;
			if (conv != 'r' && strchr(bad_chars,c)) return -EINVAL;
			if (conv == 's' && strchr(bad_if_strict,c))
				return -EINVAL;
			if (c < ' ' || c == ':' || c == '\\')
				return -EINVAL;
			if (c == '.') {
				if (conv == 's')
					return -EINVAL;
				break;
			}
			if (c >= 'A' && c <= 'Z' && conv == 's') return -EINVAL;
			space = c == ' ';
			*walk++ = c >= 'a' && c <= 'z' ? c-32 : c;
		}
		if (space) return -EINVAL;
		if (conv == 's' && len) return -EINVAL;
	}
	while (walk-res < MSDOS_NAME_MAX) *walk++ = ' ';
	for (reserved = reserved_names; *reserved; reserved++)
		if (!strncmp(res,*reserved,8)) return -EINVAL;
	return 0;
}

/* Convert a MS-DOS time/date pair to a UNIX date (seconds since 1 1 70) */
unsigned int 
msdos_date_dos2unix(unsigned short time_val,unsigned short date)
{
    int month,year,secs;

    month = ((date >> 5) & 15)-1;
    year = date >> 9;
    secs = (time_val & 31)*2+60*((time_val >> 5) & 63)+
           (time_val >> 11)*3600+86400*
        ((date & 31)-1+day_n[month]+(year/4)+year*365-((year & 3) == 0 &&
        month < 2 ? 1 : 0)+3653);
            /* days since 1.1.70 plus 80's leap day */

    return secs;
}


/* Convert linear UNIX date to a MS-DOS time/date pair */
void msdos_date_unix2dos(int unix_date,
                         unsigned short *time_val, 
                         unsigned short *date)
{
	int day,year,nl_day,month;

	*time_val = (unix_date % 60)/2+(((unix_date/60) % 60) << 5)+
	    (((unix_date/3600) % 24) << 11);
	day = unix_date/86400-3652;
	year = day/365;
	if ((year+3)/4+365*year > day) year--;
	day -= (year+3)/4+365*year;
	if (day == 59 && !(year & 3)) {
		nl_day = day;
		month = 2;
	}
	else {
		nl_day = (year & 3) || day <= 59 ? day : day-1;
		for (month = 0; month < 12; month++)
			if (day_n[month] > nl_day) break;
	}
	*date = nl_day-day_n[month-1]+1+(month << 5)+(year << 9);
}


/* msdos_get_token --
 *     Routine to get a token (name or separator) from the path.
 *
 * PARAMETERS:
 *     path      - path to get token from
 *     ret_token - returned token
 *     token_len - length of returned token
 *
 * RETURNS:
 *     token type, token and token length 
 *
 */
msdos_token_types_t 
msdos_get_token(const char *path, char *ret_token, int *token_len)
{
    int                 rc = RC_OK;
    register int        i = 0;
    msdos_token_types_t type = MSDOS_NAME;
    char                token[MSDOS_NAME_MAX_WITH_DOT+1];  
    register char       c;

    /* 
     *  Copy a name into token.  (Remember NULL is a token.)
     */
    c = path[i];
    while ( (!msdos_is_separator(c)) && (i <= MSDOS_NAME_MAX_WITH_DOT) ) 
    {
        token[i] = c;
        if ( i == MSDOS_NAME_MAX_WITH_DOT )
            return MSDOS_INVALID_TOKEN;
        if ( !msdos_is_valid_name_char(c) )
            return MSDOS_INVALID_TOKEN;   
        c = path [++i];
    }

    /*
     *  Copy a seperator into token.
     */
    if ( i == 0 ) 
    {
        token[i] = c;
        if ( token[i] != '\0' ) 
        {
            i++;
            type = MSDOS_CURRENT_DIR;
        } 
        else  
            type = MSDOS_NO_MORE_PATH;
    } 
    else if (token[ i-1 ] != '\0') 
        token[i] = '\0';

    /*
     *  Set token_len to the number of characters copied.
     */
    *token_len = i;

    /*
     *  If we copied something that was not a seperator see if
     *  it was a special name.
     */
    if ( type == MSDOS_NAME ) 
    {
        if ( strcmp( token, "..") == 0 )
        {
            strcpy(ret_token, MSDOS_DOTDOT_NAME);
            type = MSDOS_UP_DIR;
            return type;
        }  

        if ( strcmp( token, "." ) == 0 )
        {
            strcpy(ret_token, MSDOS_DOT_NAME);
            type = MSDOS_CURRENT_DIR;
            return type;        
        }

        rc = msdos_format_name('r', token, *token_len, ret_token, 0);
        if ( rc != RC_OK )
            return MSDOS_INVALID_TOKEN;     
    }
    ret_token[MSDOS_NAME_MAX] = '\0';
    return type;
}


/* msdos_find_name --
 *     Find the node which correspondes to the name, open fat-file which 
 *     correspondes to the found node and close fat-file which correspondes 
 *     to the node we searched in.
 *
 * PARAMETERS:
 *     parent_loc - parent node description
 *     name       - name to find
 *
 * RETURNS:
 *     RC_OK and updated 'parent_loc' on success, or -1 if error 
 *     occured (errno set apropriately)
 *
 */
int 
msdos_find_name(
    rtems_filesystem_location_info_t *parent_loc,
    char                             *name
    )
{
    int              rc = RC_OK;
    msdos_fs_info_t *fs_info = parent_loc->mt_entry->fs_info;
    fat_file_fd_t   *fat_fd = NULL;
    fat_auxiliary_t  aux;
    unsigned short   time_val = 0;
    unsigned short   date = 0;
    unsigned char    node_entry[MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE];
  
    memset(node_entry, 0, MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE);
  
    /* 
     * find the node which correspondes to the name in the directory pointed by 
     * 'parent_loc'
     */
    rc = msdos_get_name_node(parent_loc, name, &aux, node_entry);
    if (rc != RC_OK)
        return rc;

    /* open fat-file corresponded to the found node */
    rc = fat_file_open(parent_loc->mt_entry, aux.cln, aux.ofs, &fat_fd);
    if (rc != RC_OK)
        return rc;
  
    /*
     * I don't like this if, but: we should do it , or should write new file 
     * size and first cluster num to the disk after each write operation 
     * (even if one byte is written  - that is TOO non-optimize) because 
     * otherwise real values of these fields stored in fat-file descriptor 
     * may be accidentely rewritten with wrong values stored on the disk
     */
    if (fat_fd->links_num == 1)
    {
        fat_fd->info_cln = aux.cln;
        fat_fd->info_ofs = aux.ofs;
        fat_fd->cln = MSDOS_EXTRACT_CLUSTER_NUM(node_entry);
        fat_fd->first_char = *MSDOS_DIR_NAME(node_entry);
    
        time_val = *MSDOS_DIR_WRITE_TIME(node_entry);
        date = *MSDOS_DIR_WRITE_DATE(node_entry);
    
        fat_fd->mtime = msdos_date_dos2unix(CF_LE_W(time_val), CF_LE_W(date));
 
        if ((*MSDOS_DIR_ATTR(node_entry)) & MSDOS_ATTR_DIRECTORY)
        {
            fat_fd->fat_file_type = FAT_DIRECTORY;
            fat_fd->size_limit = MSDOS_MAX_DIR_LENGHT;                                    
  
            rc = fat_file_size(parent_loc->mt_entry, fat_fd);
            if (rc != RC_OK)
            {
                fat_file_close(parent_loc->mt_entry, fat_fd);
                return rc;
            }
        }
        else
        {
            fat_fd->fat_file_size = CF_LE_L(*MSDOS_DIR_FILE_SIZE(node_entry)); 
            fat_fd->fat_file_type = FAT_FILE;                                    
            fat_fd->size_limit = MSDOS_MAX_FILE_SIZE;
        }
        
        /* these data is not actual for zero-length fat-file */
        fat_fd->map.file_cln = 0;
        fat_fd->map.disk_cln = fat_fd->cln;
        
        if ((fat_fd->fat_file_size != 0) && 
            (fat_fd->fat_file_size <= fs_info->fat.vol.bpc))
        {
            fat_fd->map.last_cln = fat_fd->cln;
        }
        else
        {
            fat_fd->map.last_cln = FAT_UNDEFINED_VALUE;
        }
    }  

    /* close fat-file corresponded to the node we searched in */
    rc = fat_file_close(parent_loc->mt_entry, parent_loc->node_access);
    if (rc != RC_OK)
    {
        fat_file_close(parent_loc->mt_entry, fat_fd);
        return rc;
    }

    /* update node_info_ptr field */
    parent_loc->node_access = fat_fd;
  
    return rc;
}  

/* msdos_get_name_node --
 *     This routine is used in two ways: for a new mode creation (a) or for
 *     search the node which correspondes to the name parameter (b).
 *     In case (a) 'name' should be set up to NULL and 'name_dir_entry' should 
 *     point to initialized 32 bytes structure described a new node. 
 *     In case (b) 'name' should contain a valid string.
 *
 *     (a): reading fat-file which correspondes to directory we are going to 
 *          create node in. If free slot is found write contents of 
 *          'name_dir_entry' into it. If reach end of fat-file and no free 
 *          slot found, write 32 bytes to the end of fat-file.
 *
 *     (b): reading fat-file which correspondes to directory and trying to 
 *          find slot with the name field == 'name' parameter
 *
 *
 * PARAMETERS:
 *     parent_loc     - node description to create node in or to find name in
 *     name           - NULL or name to find
 *     paux           - identify a node location on the disk -
 *                      cluster num and offset inside the cluster 
 *     name_dir_entry - node to create/placeholder for found node (IN/OUT)
 *
 * RETURNS:
 *     RC_OK, filled aux_struct_ptr and name_dir_entry on success, or -1 if 
 *     error occured (errno set apropriately)
 *
 */
int
msdos_get_name_node(
    rtems_filesystem_location_info_t *parent_loc, 
    char                             *name, 
    fat_auxiliary_t                  *paux,
    char                             *name_dir_entry
    )
{
    int              rc = RC_OK;
    ssize_t          ret = 0;
    msdos_fs_info_t *fs_info = parent_loc->mt_entry->fs_info;
    fat_file_fd_t   *fat_fd = parent_loc->node_access;
    unsigned32       dotdot_cln = 0;

    /* find name in fat-file which correspondes to the directory */
    rc = msdos_find_name_in_fat_file(parent_loc->mt_entry, fat_fd, name, paux,
                                     name_dir_entry);
    if ((rc != RC_OK) && (rc != MSDOS_NAME_NOT_FOUND_ERR))
        return rc;
  
    /* if we search for valid name and name not found -> return */
    if ((rc == MSDOS_NAME_NOT_FOUND_ERR) && (name != NULL))
        return rc;
  
    /* 
     * if we try to create new entry and the directory is not big enough 
     * currently - try to enlarge directory   
     */
    if ((rc == MSDOS_NAME_NOT_FOUND_ERR) && (name == NULL))
    {
        ret = fat_file_write(parent_loc->mt_entry, fat_fd, 
                             fat_fd->fat_file_size, 
                             MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE, 
                             name_dir_entry);
        if (ret == -1)
            return -1;  

        /* on success directory is enlarged by a new cluster */
        fat_fd->fat_file_size += fs_info->fat.vol.bpc;
    
        /* get cluster num where a new node located */
        rc = fat_file_ioctl(parent_loc->mt_entry, fat_fd, F_CLU_NUM,
                            fat_fd->fat_file_size - 1, &paux->cln);
                        
        if (rc != RC_OK)
            return rc;

        /* 
         * if new cluster allocated succesfully then new node is at very 
         * beginning of the cluster (offset is computed in bytes) 
         */
        paux->ofs = 0;
        return RC_OK;                                                 
    }                                   
                                   
    /* 
     * if we have deal with ".." - it is a special case :((( 
     *
     * Really, we should return cluster num and offset not of ".." slot, but
     * slot which correspondes to real directory name.
     */
    if ((rc == RC_OK) && (name != NULL))
    {
        if (strncmp(name, MSDOS_DOTDOT_NAME, MSDOS_SHORT_NAME_LEN) == 0)
        {
            dotdot_cln = MSDOS_EXTRACT_CLUSTER_NUM((name_dir_entry));

            /* are we right under root dir ? */
            if (dotdot_cln == 0)
            { 
                /* 
                 * we can relax about first_char field - it never should be 
                 * used for root dir
                 */
                paux->cln = FAT_ROOTDIR_CLUSTER_NUM;
                paux->ofs = 0;
            }  
            else
            {
                rc = msdos_get_dotdot_dir_info_cluster_num_and_offset(
                        parent_loc->mt_entry,
                        dotdot_cln,
                        paux,
                        name_dir_entry
                        );
                if (rc != RC_OK)
                    return rc;
            }
        }  
    }
    return rc;
}

/*
 * msdos_get_dotdot_dir_info_cluster_num_and_offset
 *
 * Unfortunately, in general, we cann't work here in fat-file ideologic 
 * (open fat_file "..", get ".." and ".", open "..", find an entry ...) 
 * because if we open
 * fat-file ".." it may happend that we have two different fat-file
 * descriptors ( for real name of directory and ".." name ) for a single 
 * file  ( cluster num of both pointers to the same cluster )
 * But...we do it because we protected by semaphore
 * 
 */

/* msdos_get_dotdot_dir_info_cluster_num_and_offset --
 *     Get cluster num and offset not of ".." slot, but slot which correspondes 
 *     to real directory name.   
 *
 * PARAMETERS:
 *     mt_entry       - mount table entry
 *     cln            - data cluster num extracted drom ".." slot
 *     paux           - identify a node location on the disk -
 *                      number of cluster and offset inside the cluster
 *     dir_entry      - placeholder for found node 
 *
 * RETURNS:
 *     RC_OK, filled 'paux' and 'dir_entry' on success, or -1 if error occured
 *     (errno set apropriately)
 *
 */
int
msdos_get_dotdot_dir_info_cluster_num_and_offset(
    rtems_filesystem_mount_table_entry_t *mt_entry, 
    unsigned32                            cln,
    fat_auxiliary_t                      *paux,
    char                                 *dir_entry
    )
{
    int              rc = RC_OK;
    msdos_fs_info_t *fs_info = mt_entry->fs_info;
    fat_file_fd_t   *fat_fd = NULL;
    unsigned char    dot_node[MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE];
    unsigned char    dotdot_node[MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE];
    unsigned char    cur_node[MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE];
    unsigned32       cl4find = 0;
  
    memset(dot_node, 0, MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE);  
    memset(dotdot_node, 0, MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE);
    memset(cur_node, 0, MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE);
  
    /*
     * open fat-file corresponded to ".."
     */
    rc = fat_file_open(mt_entry, paux->cln, paux->ofs, &fat_fd);
    if (rc != RC_OK)
        return rc;
  
    fat_fd->info_cln = paux->cln;
    fat_fd->info_ofs = paux->ofs;
    fat_fd->cln = cln;
    fat_fd->fat_file_type = FAT_DIRECTORY;
    fat_fd->size_limit = MSDOS_MAX_DIR_LENGHT;
    
    fat_fd->map.file_cln = 0;
    fat_fd->map.disk_cln = fat_fd->cln;

    rc = fat_file_size(mt_entry, fat_fd);
    if (rc != RC_OK)
    {
        fat_file_close(mt_entry, fat_fd);
        return rc;
    }  
  
    /* find "." node in opened directory */
    rc = msdos_find_name_in_fat_file(mt_entry, fat_fd, MSDOS_DOT_NAME, paux, 
                                     dot_node);
                                   
    if (rc != RC_OK)
    {
        fat_file_close(mt_entry, fat_fd);
        return rc;
    }
  
    /* find ".." node in opened directory */
    rc = msdos_find_name_in_fat_file(mt_entry, fat_fd, MSDOS_DOTDOT_NAME, paux, 
                                     dotdot_node);
                                   
    if (rc != RC_OK) 
    {
        fat_file_close(mt_entry, fat_fd);
        return rc;
    }

    cl4find = MSDOS_EXTRACT_CLUSTER_NUM(dot_node);
  
    /* close fat-file corresponded to ".." directory */
    rc = fat_file_close(mt_entry, fat_fd);
    if ( rc != RC_OK )
        return rc;

    if ( (MSDOS_EXTRACT_CLUSTER_NUM(dotdot_node)) == 0)
    {
        /* 
         * we handle root dir for all FAT types in the same way with the 
         * ordinary directories ( through fat_file_* calls )
         */
        paux->cln = FAT_ROOTDIR_CLUSTER_NUM;
        paux->ofs = 0;
    }

    /* open fat-file corresponded to second ".." */
    rc = fat_file_open(mt_entry, paux->cln, paux->ofs, &fat_fd);
    if (rc != RC_OK)
        return rc;

    fat_fd->info_cln = paux->cln;
    fat_fd->info_ofs = paux->ofs;

    if ((MSDOS_EXTRACT_CLUSTER_NUM(dotdot_node)) == 0)
        fat_fd->cln = fs_info->fat.vol.rdir_cl;
    else
        fat_fd->cln = MSDOS_EXTRACT_CLUSTER_NUM(dotdot_node);

    fat_fd->fat_file_type = FAT_DIRECTORY;
    fat_fd->size_limit = MSDOS_MAX_DIR_LENGHT;
    
    fat_fd->map.file_cln = 0;
    fat_fd->map.disk_cln = fat_fd->cln;

    rc = fat_file_size(mt_entry, fat_fd);
    if (rc != RC_OK)
    {
        fat_file_close(mt_entry, fat_fd);
        return rc;
    }  
  
    /* in this directory find slot with specified cluster num */
    rc = msdos_find_node_by_cluster_num_in_fat_file(mt_entry, fat_fd, cl4find, 
                                                    paux, dir_entry);
    if (rc != RC_OK)
    {
        fat_file_close(mt_entry, fat_fd);
        return rc;
    }
    rc = fat_file_close(mt_entry, fat_fd);
    return rc;
}


/* msdos_set_dir_wrt_time_and_date --
 *     Write last write date and time for a file to the disk (to corresponded 
 *     32bytes node)      
 *
 * PARAMETERS:
 *     mt_entry - mount table entry 
 *     fat_fd   - fat-file descriptor
 *
 * RETURNS:
 *     RC_OK on success, or -1 if error occured (errno set apropriately).
 *
 */
int
msdos_set_dir_wrt_time_and_date(
    rtems_filesystem_mount_table_entry_t *mt_entry, 
    fat_file_fd_t                        *fat_fd
    )
{
    ssize_t          ret1 = 0, ret2 = 0;
    msdos_fs_info_t *fs_info = mt_entry->fs_info;
    unsigned short   time_val;
    unsigned short   date;
    unsigned32       sec = 0;
    unsigned32       byte = 0;
  
    msdos_date_unix2dos(fat_fd->mtime, &time_val, &date);
  
    /* 
     * calculate input for _fat_block_write: convert (cluster num, offset) to
     * (sector num, new offset)
     */
    sec = fat_cluster_num_to_sector_num(mt_entry, fat_fd->info_cln);
    sec += (fat_fd->info_ofs >> fs_info->fat.vol.sec_log2);
    /* byte points to start of 32bytes structure */
    byte = fat_fd->info_ofs & (fs_info->fat.vol.bps - 1);
   
    time_val = CT_LE_W(time_val);
    ret1 = _fat_block_write(mt_entry, sec, byte + MSDOS_FILE_WTIME_OFFSET,
                            2, (char *)(&time_val));
    date = CT_LE_W(date);
    ret2 = _fat_block_write(mt_entry, sec, byte + MSDOS_FILE_WDATE_OFFSET,
                            2, (char *)(&date));

    if ( (ret1 < 0) || (ret2 < 0) )
        return -1;

    return RC_OK;
}

/* msdos_set_first_cluster_num --
 *     Write number of first cluster of the file to the disk (to corresponded 
 *     32bytes slot)      
 *
 * PARAMETERS:
 *     mt_entry - mount table entry 
 *     fat_fd   - fat-file descriptor
 *
 * RETURNS:
 *     RC_OK on success, or -1 if error occured
 *
 */
int
msdos_set_first_cluster_num(
    rtems_filesystem_mount_table_entry_t *mt_entry, 
    fat_file_fd_t                        *fat_fd
    )
{
    ssize_t          ret1 = 0, ret2 = 0;
    msdos_fs_info_t *fs_info = mt_entry->fs_info;
    unsigned32       new_cln = fat_fd->cln;
    unsigned16       le_cl_low = 0;
    unsigned16       le_cl_hi = 0;  
    unsigned32       sec = 0;
    unsigned32       byte = 0;

    /* 
     * calculate input for _fat_block_write: convert (cluster num, offset) to
     * (sector num, new offset)
     */
    sec = fat_cluster_num_to_sector_num(mt_entry, fat_fd->info_cln);
    sec += (fat_fd->info_ofs >> fs_info->fat.vol.sec_log2);
    /* byte from points to start of 32bytes structure */
    byte = fat_fd->info_ofs & (fs_info->fat.vol.bps - 1);
 
    le_cl_low = CT_LE_W((unsigned16)(new_cln & 0x0000FFFF));
    ret1 = _fat_block_write(mt_entry, sec, 
                            byte + MSDOS_FIRST_CLUSTER_LOW_OFFSET, 2,
                            (char *)(&le_cl_low));
    le_cl_hi = CT_LE_W((unsigned16)((new_cln & 0xFFFF0000) >> 16));
    ret2 = _fat_block_write(mt_entry, sec,
                            byte + MSDOS_FIRST_CLUSTER_HI_OFFSET, 2,
                            (char *)(&le_cl_hi));
    if ( (ret1 < 0) || (ret2 < 0) )
        return -1;

    return RC_OK;
}


/* msdos_set_file size --
 *     Write file size of the file to the disk (to corresponded 32bytes slot)      
 *
 * PARAMETERS:
 *     mt_entry - mount table entry 
 *     fat_fd   - fat-file descriptor
 *
 * RETURNS:
 *     RC_OK on success, or -1 if error occured (errno set apropriately).
 *
 */
int
msdos_set_file_size(
    rtems_filesystem_mount_table_entry_t *mt_entry, 
    fat_file_fd_t                        *fat_fd
    )
{
    ssize_t          ret = 0;
    msdos_fs_info_t *fs_info = mt_entry->fs_info;
    unsigned32       le_new_length = 0;
    unsigned32       sec = 0;
    unsigned32       byte = 0;

    sec = fat_cluster_num_to_sector_num(mt_entry, fat_fd->info_cln);
    sec += (fat_fd->info_ofs >> fs_info->fat.vol.sec_log2);
    byte = (fat_fd->info_ofs & (fs_info->fat.vol.bps - 1));

    le_new_length = CT_LE_L((fat_fd->fat_file_size));
    ret = _fat_block_write(mt_entry, sec, byte + MSDOS_FILE_SIZE_OFFSET, 4,
                           (char *)(&le_new_length));
    if ( ret < 0 )
        return -1;

    return RC_OK;
}

/*
 * We should not check whether this routine is called for root dir - it
 * never can happend
 */

/* msdos_set_first_char4file_name --
 *     Write first character of the name of the file to the disk (to 
 *     corresponded 32bytes slot)      
 *
 * PARAMETERS:
 *     mt_entry - mount table entry 
 *     cl       - number of cluster
 *     ofs      - offset inside cluster
 *     fchar    - character to set up
 *
 * RETURNS:
 *     RC_OK on success, or -1 if error occured (errno set apropriately)
 *
 */
int 
msdos_set_first_char4file_name(
    rtems_filesystem_mount_table_entry_t *mt_entry, 
    unsigned32                            cl,
    unsigned32                            ofs,
    unsigned char                         fchar
    )
{
    ssize_t          ret = 0;
    msdos_fs_info_t *fs_info = mt_entry->fs_info;
    unsigned32       sec = 0;
    unsigned32       byte = 0;

    sec = fat_cluster_num_to_sector_num(mt_entry, cl);
    sec += (ofs >> fs_info->fat.vol.sec_log2);
    byte = (ofs & (fs_info->fat.vol.bps - 1));

    ret = _fat_block_write(mt_entry, sec, byte + MSDOS_FILE_NAME_OFFSET, 1, 
                           &fchar);
    if ( ret < 0)
        return -1;

    return  RC_OK;
}                        

/* msdos_dir_is_empty --
 *     Check whether directory which correspondes to the fat-file descriptor is 
 *     empty.      
 *
 * PARAMETERS:
 *     mt_entry - mount table entry 
 *     fat_fd   - fat-file descriptor   
 *     ret_val  - placeholder for result 
 *
 * RETURNS:
 *     RC_OK on success, or -1 if error occured
 *
 */
int
msdos_dir_is_empty(
    rtems_filesystem_mount_table_entry_t *mt_entry,
    fat_file_fd_t                        *fat_fd, 
    rtems_boolean                        *ret_val
    )
{
    ssize_t          ret = 0;
    msdos_fs_info_t *fs_info = mt_entry->fs_info;
    unsigned32       j = 0, i = 0;
  
    /* dir is not empty */
    *ret_val = FALSE;

    while ((ret = fat_file_read(mt_entry, fat_fd, j * fs_info->fat.vol.bps,
                                  fs_info->fat.vol.bps, 
                                  fs_info->cl_buf)) != FAT_EOF)
    {
        if (ret < MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE)
            return -1;

        assert(ret == fs_info->fat.vol.bps);
        
        for (i = 0; 
             i < fs_info->fat.vol.bps; 
             i += MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE)
        {
            if (((*MSDOS_DIR_NAME(fs_info->cl_buf + i)) == 
                 MSDOS_THIS_DIR_ENTRY_EMPTY) || 
                (strncmp(MSDOS_DIR_NAME((fs_info->cl_buf + i)), MSDOS_DOT_NAME, 
                         MSDOS_SHORT_NAME_LEN) == 0) ||
                (strncmp(MSDOS_DIR_NAME((fs_info->cl_buf + i)),
                         MSDOS_DOTDOT_NAME, 
                         MSDOS_SHORT_NAME_LEN) == 0))
                continue;

            if ((*MSDOS_DIR_NAME(fs_info->cl_buf + i)) ==  
                MSDOS_THIS_DIR_ENTRY_AND_REST_EMPTY)
            {
                *ret_val = TRUE;
                return RC_OK;
            }
            return RC_OK;
        }    
        j++; 
    }
    *ret_val = TRUE;
    return RC_OK;
}


/* msdos_find_name_in_fat_file --
 *     This routine is used in two ways: for a new mode creation (a) or for
 *     search the node which correspondes to the 'name' parameter (b).
 *     In case (a) name should be set up to NULL and 'name_dir_entry' should 
 *     point to initialized 32 bytes structure described a new node. 
 *     In case (b) 'name' should contain a valid string.
 *
 *     (a): reading fat-file corresponded to directory we are going to create
 *          node in. If found free slot write contents of name_dir_entry into
 *          it.  
 *
 *     (b): reading fat-file corresponded to directory and trying to find slot
 *          with the name field == name parameter
 *
 * PARAMETERS:
 *     mt_entry       - mount table entry 
 *     fat_fd         - fat-file descriptor   
 *     name           - NULL or name to find 
 *     paux           - identify a node location on the disk -
 *                      number of cluster and offset inside the cluster
 *     name_dir_entry - node to create/placeholder for found node
 *
 * RETURNS:
 *     RC_OK on success, or error code if error occured (errno set 
 *     appropriately)
 *
 */
int
msdos_find_name_in_fat_file(
    rtems_filesystem_mount_table_entry_t *mt_entry,
    fat_file_fd_t                        *fat_fd, 
    char                                 *name, 
    fat_auxiliary_t                      *paux,
    char                                 *name_dir_entry
    )
{
    int              rc = RC_OK;
    ssize_t          ret = 0;
    msdos_fs_info_t *fs_info = mt_entry->fs_info;
    unsigned32       i = 0, j = 0;
    unsigned32       bts2rd = 0;

    if (FAT_FD_OF_ROOT_DIR(fat_fd) && 
       (fs_info->fat.vol.type & (FAT_FAT12 | FAT_FAT16)))
        bts2rd = fat_fd->fat_file_size;  
    else
        bts2rd = fs_info->fat.vol.bpc;
  
    while ((ret = fat_file_read(mt_entry, fat_fd, (j * bts2rd), bts2rd, 
                                fs_info->cl_buf)) != FAT_EOF) 
    {
        if (ret < MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE)
            set_errno_and_return_minus_one(EIO);
            
        assert(ret == bts2rd);    

        for (i = 0; i < bts2rd; i += MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE)
        {
            /* is the entry empty ? */
            if (((*MSDOS_DIR_NAME(fs_info->cl_buf + i)) == 
                 MSDOS_THIS_DIR_ENTRY_AND_REST_EMPTY) ||
                 ((*MSDOS_DIR_NAME(fs_info->cl_buf + i)) == 
                 MSDOS_THIS_DIR_ENTRY_EMPTY))
            {
                /* whether we are looking for an empty entry */
                if (name == NULL)
                {
                    /* get current cluster number */
                    rc = fat_file_ioctl(mt_entry, fat_fd, F_CLU_NUM, 
                                        j * bts2rd, &paux->cln);
                    if (rc != RC_OK)
                        return rc;

                    /* offset is computed in bytes */
                    paux->ofs = i;
          
                    /* write new node entry */
                    ret = fat_file_write(mt_entry, fat_fd, j * bts2rd + i, 
                                         MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE,
                                         name_dir_entry);
                    if (ret != MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE)
                        return -1;

                    /* 
                     * we don't update fat_file_size here - it should not 
                     * increase 
                     */
                    return RC_OK;
                }

                /* 
                 * if name != NULL and there is no more entries in the 
                 * directory - return name-not-found
                 */
                if (((*MSDOS_DIR_NAME(fs_info->cl_buf + i)) == 
                     MSDOS_THIS_DIR_ENTRY_AND_REST_EMPTY))
                    return MSDOS_NAME_NOT_FOUND_ERR;
            }     
            else
            {
                /* entry not empty and name != NULL -> compare names */
                if (name != NULL)
                {
                    if (strncmp(MSDOS_DIR_NAME((fs_info->cl_buf + i)), name, 
                                MSDOS_SHORT_NAME_LEN) == 0)
                    {
                        /* 
                         * we get the entry we looked for - fill auxiliary 
                         * structure and copy all 32 bytes of the entry
                         */
                        rc = fat_file_ioctl(mt_entry, fat_fd, F_CLU_NUM, 
                                            j * bts2rd, &paux->cln);
                        if (rc != RC_OK)
                            return rc;

                        /* offset is computed in bytes */
                        paux->ofs = i;
                        memcpy(name_dir_entry,(fs_info->cl_buf + i),
                               MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE);
                        return RC_OK;
                    }
                }  
            }      
        }                          
        j++;
    }                        
    return MSDOS_NAME_NOT_FOUND_ERR;
}

/* msdos_find_node_by_cluster_num_in_fat_file --
 *     Find node with specified number of cluster in fat-file.
 *
 * PARAMETERS:
 *     mt_entry  - mount table entry 
 *     fat_fd    - fat-file descriptor   
 *     cl4find   - number of cluster to find 
 *     paux      - identify a node location on the disk -
 *                 cluster num and offset inside the cluster
 *     dir_entry - placeholder for found node
 *
 * RETURNS:
 *     RC_OK on success, or error code if error occured
 *
 */
int
msdos_find_node_by_cluster_num_in_fat_file(
    rtems_filesystem_mount_table_entry_t *mt_entry,
    fat_file_fd_t                        *fat_fd,
    unsigned32                            cl4find, 
    fat_auxiliary_t                      *paux,
    char                                 *dir_entry
    )
{
    int              rc = RC_OK;
    ssize_t          ret = 0;
    msdos_fs_info_t *fs_info = mt_entry->fs_info;
    unsigned32       bts2rd = 0;  
    unsigned32       i = 0, j = 0;

    if (FAT_FD_OF_ROOT_DIR(fat_fd) && 
       (fs_info->fat.vol.type & (FAT_FAT12 | FAT_FAT16)))
        bts2rd = fat_fd->fat_file_size;  
    else
        bts2rd = fs_info->fat.vol.bpc;

    while ((ret = fat_file_read(mt_entry, fat_fd, j * bts2rd, bts2rd,
                                  fs_info->cl_buf)) != FAT_EOF)
    {
        if ( ret < MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE )
            set_errno_and_return_minus_one( EIO );

        assert(ret == bts2rd);
        
        for (i = 0; i < bts2rd; i += MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE)
        {
            /* if this and all rest entries are empty - return not-found */
            if ((*MSDOS_DIR_NAME(fs_info->cl_buf + i)) ==
                MSDOS_THIS_DIR_ENTRY_AND_REST_EMPTY)
                return MSDOS_NAME_NOT_FOUND_ERR;

            /* if this entry is empty - skip it */ 
            if ((*MSDOS_DIR_NAME(fs_info->cl_buf + i)) ==
                MSDOS_THIS_DIR_ENTRY_EMPTY)
                continue;

            /* if get a non-empty entry - compare clusters num */
            if (MSDOS_EXTRACT_CLUSTER_NUM((fs_info->cl_buf + i)) == cl4find)
            {
                /* on success fill aux structure and copy all 32 bytes */
                rc = fat_file_ioctl(mt_entry, fat_fd, F_CLU_NUM, j * bts2rd, 
                                    &paux->cln);
                if (rc != RC_OK)
                    return rc;

                paux->ofs = i;
                memcpy(dir_entry, fs_info->cl_buf + i, 
                       MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE);
                return RC_OK;
            }
        }                          
        j++;
    }                        
    return MSDOS_NAME_NOT_FOUND_ERR;
}
